package xj.graph2d;

import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import xj.graph2d.graph.AbstractGraphNode;
import xj.graph2d.graph.AbstractHyperGraphNode;
import xj.graph2d.graph.GraphConstants;
import xj.graph2d.graph.GraphEdge;
import xj.graph2d.graph.GraphNode;
import xj.graph2d.graph.HyperGraphNode;
import xj.graph2d.graph.Subgraph;
import xj.lang.CodeWriter;

public abstract class GraphHyperNodeBase 
  extends GraphNodeBase 
  implements HyperGraphNode<GraphNodeBase, GraphEdgeBase>, TwoEndsShape {

  protected GraphHyperNodeBase() {
    super(-1); // no graph component
  }

  protected GraphHyperNodeBase(boolean createComp) {
    super(createComp ? GraphConstants.HYPERNODE : -1);
  }

  protected GraphHyperNodeBase(int comptype) {
    super(comptype);
  }

  @Override
  protected void initComponent(int graphComponentType) {
    super.initComponent(graphComponentType);
    if (comp instanceof HyperGraphNode) {
      graph = (HyperGraphNode) comp;
      graph.setAllowDanglingEdges(true);
      graph.setAllowDuplicateEdges(true);
    }
  }

  @Override
  public void initComponent(String compid) {
    if (acceptSubshape) {
      AbstractHyperGraphNode hnode = new AbstractHyperGraphNode();
      graph = hnode;
      comp = hnode;
    } else {
      comp = new AbstractGraphNode();
    }
    if (comp != null && compid != null) {
      comp.setId(compid);
    }
  }

  public boolean isHyperNode() {
    return (graph != null);
  }

  @Override
  public boolean isEdgeSensitive() {
    return true;
  }

  @Override
  public boolean isCornorSensitive() {
    return true;
  }

  @Override
  public int addShape(DrawShape s, boolean select) {
    return addShape(s, select, false); 
  }

  public int addShape(DrawShape s, boolean select, boolean shapeOnly) {
    if (s != null) {
      if (!shapeOnly && 
	  graph != null) {
	if (s instanceof GraphNodeBase) {
	  addNode((GraphNodeBase) s);
	} else if (s instanceof GraphEdgeBase) {
	  addEdge((GraphEdgeBase) s, isAllowDuplicateEdges());
	}
      }
      return super.addShape(s, select);
    }
    return -1;
  }

  @Override
  public void insertShapeAt(DrawShape s, int i, boolean select) {
    if (s != null) {
      if (graph != null) {
	if (s instanceof GraphNodeBase) {
	  addNode((GraphNodeBase) s);
	} else if (s instanceof GraphEdgeBase) {
	  addEdge((GraphEdgeBase) s, true);
	}
      }
      super.insertShapeAt(s, i, select);
    }
  }

  @Override
  public DrawShape removeShape(int i) {
    DrawShape s = super.removeShape(i);
    if (s != null) {
      if (graph != null) {
	if (s instanceof GraphNodeBase) {
	  removeNode((GraphNodeBase) s);
	} else if (s instanceof GraphEdgeBase) {
	  removeEdge((GraphEdgeBase) s);
	}
      }
    }
    return s;
  }

  @Override
  public boolean removeShape(DrawShape s) {
    return removeShape(s, true, false);
  }

  public boolean removeShape(DrawShape s, boolean disconnect) {
    return removeShape(s, disconnect, false);
  }

  public boolean removeShape(DrawShape s, 
			     boolean disconnect,
			     boolean shapeOnly) {
    if (s != null) {
      if (graph != null) {
	if (!shapeOnly) { 
	  if (s instanceof GraphNodeBase) {
	    removeNode((GraphNodeBase) s, disconnect);
	  } else if (s instanceof GraphEdgeBase) {
	    removeEdge((GraphEdgeBase) s);
	  }
	}
      }
      return super.removeShape(s);
    }
    return false;
  }

  @Override
  public void removeAllShapes() {
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = n - 1; i >= 0; i--) {
	DrawShape s = subshapes.get(i);
	if (s != null) {
	  s.setParent(null);
	  if (graph != null) {
	    if (s instanceof GraphNodeBase) {
	      removeNode((GraphNodeBase) s);
	    } else if (s instanceof GraphEdgeBase) {
	      removeEdge((GraphEdgeBase) s);
	    }
	  }
	}
      }
      subshapes.clear();
    }
  }

  // from Subgraph

  public boolean isExpanded() {
    if (graph != null) {
      return graph.isExpanded();
    }
    return true;
  }

  public void expand() {
    if (graph != null) {
      graph.expand();
    }
  }

  public void collapse() {
    if (graph != null) {
      graph.collapse();
    }
  }

  public boolean addNode(GraphNodeBase node) {
    boolean result = false; 
    if (graph != null) {
      result = graph.addNode(node);
      node.setGraph(getGraph());
      node.setSubgraph(this);
    }
    return result;
  }

  public boolean removeNode(GraphNodeBase node) {
    return removeNode(node, true);
  }

  public boolean removeNode(GraphNodeBase node, boolean disconnect) {
    if (graph != null) {
      return graph.removeNode(node, disconnect);
    }
    return false;
  }

  public GraphNodeBase removeNode(String name) {
    return removeNode(name, true);
  }

  public GraphNodeBase removeNode(String name, boolean disconnect) {
    if (graph != null) {
      return graph.removeNode(name, disconnect);
    }
    return null;
  }

  public boolean addEdge(GraphEdgeBase edge, boolean allowDuplicate) {
    boolean result = false;
    if (graph != null) {
      result = graph.addEdge(edge, allowDuplicate);
      edge.setGraph(getGraph());
      edge.setSubgraph(this);
    }
    return result;
  }

  public boolean addEdge(GraphEdgeBase edge) {
    boolean allowDuplicate = isAllowDuplicateEdges(); 
    return addEdge(edge, allowDuplicate);
  }

  public boolean removeEdge(GraphEdgeBase edge) {
    if (graph != null) {
      return graph.removeEdge(edge);
    }
    return false;
  }

  public GraphEdgeBase removeEdge(String name) {
    if (graph != null) {
      return graph.removeEdge(name);
    }
    return null;
  }

  public GraphNodeBase disconnectDestination(GraphEdgeBase edge) {
    if (graph != null) {
      return graph.disconnectDestination(edge);
    }
    return null;
  }

  public GraphNodeBase disconnectSource(GraphEdgeBase edge) {
    if (graph != null) {
      return graph.disconnectSource(edge);
    }
    return null;
  }

  public void connectDestination(GraphEdgeBase edge, GraphNodeBase node) {
    if (graph != null) {
      graph.connectDestination(edge, node);
    }
  }

  public void connectSource(GraphEdgeBase edge, GraphNodeBase node) {
    if (graph != null) {
      graph.connectSource(edge, node);
    }
  }

  public void reverseEdgeDirection(GraphEdgeBase edge) { 
    if (graph != null) {
      graph.reverseEdgeDirection(edge);
    }
  }

  public void moveEdgeToSubgraph(GraphEdgeBase e, Subgraph subg) {
    if (e != null && 
	subg != null) { 
      if (e instanceof GraphEdgeBase) { 
	GraphEdgeBase edge = (GraphEdgeBase) e;
	removeShape(edge, false, true); // remove shape only 

	if (subg instanceof GraphDocument) { 
	  GraphDocument gdoc = (GraphDocument) subg;
	  gdoc.addDrawShapeOnly(edge, false);
	  //gdoc.shapeAdded(edge);
	} else if (subg instanceof GraphHyperNodeBase) { 
	  GraphHyperNodeBase hnode = (GraphHyperNodeBase) subg; 
	  hnode.addShape(edge, false, true); // add shape only 
	  //shapeAdded(edge); 
	}
      }
      if (graph != null) {
	graph.moveEdgeToSubgraph(e, subg);
      }
    }
  }

  public boolean isAllowDanglingEdges() {
    if (graph != null) {
      return graph.isAllowDanglingEdges();
    }
    return false;
  }

  public void setAllowDanglingEdges(boolean allowDanglingEdges) {
    if (graph != null) {
      graph.setAllowDanglingEdges(allowDanglingEdges);
    }
  }

  public boolean isAllowDuplicateEdges() {
    if (graph != null) {
      return graph.isAllowDuplicateEdges();
    }
    return false;
  }

  public void setAllowDuplicateEdges(boolean allowDuplicateEdges) {
    if (graph != null) {
      graph.setAllowDuplicateEdges(allowDuplicateEdges);
    }
  }

  public void removeAllNodes() {
    if (graph != null) {
      graph.removeAllNodes();
    }
  }

  public int getNumberOfNodes() {
    if (graph != null) {
      return graph.getNumberOfNodes();
    }
    return 0;
  }

  public int getNumberOfEdges() {
    if (graph != null) {
      return graph.getNumberOfEdges();
    }
    return 0;
  }

  public boolean isEmpty() {
    if (graph != null) {
      return graph.isEmpty();
    } else {
      return true;
    }
  }

  public boolean addSubgraph(Subgraph<GraphNodeBase, GraphEdgeBase> subg) {
    if (graph != null) {
      return graph.addSubgraph(subg);
    }
    return false;
  }

  public boolean removeSubgraph(Subgraph<GraphNodeBase, GraphEdgeBase> subg) {
    if (graph != null) {
      return graph.removeSubgraph(subg);
    }
    return false;
  }

  public boolean removeSubgraph(String name) {
    if (graph != null) {
      return graph.removeSubgraph(name);
    }
    return false;
  }

  public boolean containsNode(GraphNodeBase node) {
    if (graph != null) {
      return graph.containsNode(node);
    }
    return false;
  }

  public boolean containsEdge(GraphEdgeBase edge) {
    if (graph != null) {
      return graph.containsEdge(edge);
    }
    return false;
  }

  public Set allSubnodes() {
    if (graph != null) {
      return graph.allSubnodes();
    }
    return null;
  }

  public boolean containsSubnode(GraphNodeBase node) {
    if (graph != null) {
      return graph.containsSubnode(node);
    }
    return false;
  }

  public GraphNodeBase findNodeByName(String name) {
    if (graph != null) {
      return graph.findNodeByName(name);
    }
    return null;
  }

  public GraphNodeBase findNodeByName(String name, boolean deep) {
    if (graph != null) {
      return graph.findNodeByName(name, deep);
    }
    return null;
  }

  public GraphEdgeBase findEdgeByName(String name) {
    if (graph != null) {
      return graph.findEdgeByName(name);
    }
    return null;
  }

  public GraphEdgeBase findEdgeByName(String name, boolean deep) {
    if (graph != null) {
      return graph.findEdgeByName(name, deep);
    }
    return null;
  }

  public Subgraph<GraphNodeBase, GraphEdgeBase> findSubgraphByName(String name) {
    if (graph != null) {
      return graph.findSubgraphByName(name);
    }
    return null;
  }

  public Subgraph<GraphNodeBase, GraphEdgeBase> findSubgraphByName(String name, boolean deep) {
    if (graph != null) {
      return graph.findSubgraphByName(name, deep);
    }
    return null;
  }

  public Iterator<GraphNodeBase> nodes() {
    if (graph != null) {
      return graph.nodes();
    }
    return null;
  }

  public Iterator<GraphEdgeBase> edges() {
    if (graph != null) {
      return graph.edges();
    }
    return null;
  }

  public Iterator<Subgraph<GraphNodeBase, GraphEdgeBase>> subgraphs() {
    if (graph != null) {
      return graph.subgraphs();
    }
    return null;
  }

  public Set<GraphNodeBase> getNodeSet() {
    if (graph != null) {
      return graph.getNodeSet();
    }
    return null;
  }

  public Set<GraphEdgeBase> getEdgeSet() {
    if (graph != null) {
      return graph.getEdgeSet();
    }
    return null;
  }

  public Set<Subgraph<GraphNodeBase, GraphEdgeBase>> getSubgraphSet() {
    if (graph != null) {
      return graph.getSubgraphSet();
    }
    return null;
  }

  public Set<GraphEdgeBase> leavingEdges(GraphNodeBase node) {
    if (graph != null) {
      return graph.leavingEdges(node);
    }
    return null;
  }

  public Set<GraphEdgeBase> leavingEdges(GraphNodeBase node, int edgeType) {
    if (graph != null) {
      return graph.leavingEdges(node, edgeType);
    }
    return null;
  }

  public Set<GraphEdgeBase> arrivingEdges(GraphNodeBase node) {
    if (graph != null) {
      return graph.arrivingEdges(node);
    }
    return null;
  }

  public Set<GraphEdgeBase> arrivingEdges(GraphNodeBase node, int edgeType) {
    if (graph != null) {
      return graph.arrivingEdges(node, edgeType);
    }
    return null;
  }

  public int getDegree(GraphNodeBase node) {
    if (graph != null) {
      return graph.getDegree(node);
    }
    return 0;
  }

  public int getInDegree(GraphNodeBase node) {
    if (graph != null) {
      return graph.getInDegree(node);
    }
    return 0;
  }

  public int getOutDegree(GraphNodeBase node) {
    if (graph != null) {
      return graph.getOutDegree(node);
    }
    return 0;
  }

  public GraphNodeBase searchNode(Point2D p) {
    if (graph != null) {
      return graph.searchNode(p);
    }
    return null;
  }

  // from GraphNodeBase
  @Override
  public void move(double dx, double dy) {
    super.move((float) dx, (float) dy);
    /*
     * if (graph != null) { graph.move(dx, dy); }
     */
  }

  // from DrawShape
  @Override
  public void move(float dx, float dy) {
    super.move(dx, dy);
  }

  public void setNodeAttribute(String name, Object value) {
    if (graph != null) {
      graph.setNodeAttribute(name, value);
    }
  }

  public void removeNodeAttribute(String name) {
    if (graph != null) {
      graph.removeNodeAttribute(name);
    }
  }

  public Object getNodeAttribute(String key) {
    if (graph != null) {
      return graph.getNodeAttribute(key);
    }
    return null;
  }

  public void setEdgeAttribute(String name, Object value) {
    if (graph != null) {
      graph.setEdgeAttribute(name, value);
    }
  }

  public void removeEdgeAttribute(String name) {
    if (graph != null) {
      graph.removeEdgeAttribute(name);
    }
  }

  public Object getEdgeAttribute(String key) {
    if (graph != null) {
      return graph.getEdgeAttribute(key);
    }
    return null;
  }

  public void printSubgraph(PrintWriter out) {
    if (graph != null) {
      graph.printSubgraph(out);
    }
  }

  /*
   * public void print() { if (graph != null) { graph.print(); } }
   */

  public void printSubgraph(int level) {
    if (graph != null) {
      graph.printSubgraph(level);
    }
  }

  public void writeSubgraphXML(CodeWriter out, String namespacePrefix) {
    if (graph != null) {
      graph.writeSubgraphXML(out, namespacePrefix);
    }
  }

  public void buildSubgraphFromXML(org.w3c.dom.Node node, 
				   String uri0,
				   Map compShapeMap) {
    if (graph != null) { 
      graph.buildSubgraphFromXML(node, uri0, compShapeMap);
    }
  }

  public void setParentOfGraphComponents(GraphDocument gdoc) { 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = n - 1; i >= 0; i--) {
	DrawShape s = subshapes.get(i);
	if (s instanceof GraphComponentShape) {
	  GraphComponentShape comp = (GraphComponentShape) s;
	  comp.setSubgraph(this);
	  comp.setGraph(gdoc);
	  if (s instanceof GraphHyperNodeBase) { 
	    GraphHyperNodeBase hnode = (GraphHyperNodeBase) s;
	    hnode.setParentOfGraphComponents(gdoc);
	  }
	}
      }
    }
  }

  public void layoutEdges() {
    Iterator edges = graph.edges();
    while (edges.hasNext()) {
      GraphEdgeBase edge = (GraphEdgeBase) edges.next();
      if (edge instanceof GraphEdgeBase) {
	GraphEdgeBase e = (GraphEdgeBase) edge;
	e.doLayout();
      }
    }
  }

  protected HyperGraphNode<GraphNodeBase, GraphEdgeBase> graph;

}
